﻿using DatabaseProxy.Attributes;
using System.Linq.Expressions;
using System.Reflection;

namespace DatabaseProxy;
public class SqlVisitor : ExpressionVisitor
{
    private Queue<string> _sqlQueue = new();

    public SqlVisitor()
    {
        _sqlQueue.Clear();
        _sqlQueue.Enqueue(" WHERE ");
    }

    public string GetSql()
    {
        var sql = string.Join(' ', _sqlQueue);
        _sqlQueue.Clear();
        return sql;
    }

    protected override Expression VisitBinary(BinaryExpression node)
    {
        _sqlQueue.Enqueue(" ( ");
        var left = Visit(node.Left);
        _sqlQueue.Enqueue(GetNodeTypeSql(node.NodeType));
        var right = Visit(node.Right);
        _sqlQueue.Enqueue(" ) ");
        return node;
    }

    protected override Expression VisitConstant(ConstantExpression node)
    {
        if (node.Type == typeof(string))
        {
            _sqlQueue.Enqueue($" '{node.Value}' ");
        }
        else
        {
            _sqlQueue.Enqueue($" {node.Value} ");
        }
        return node;
    }

    protected override Expression VisitMember(MemberExpression node)
    {
        if (node.Expression!.NodeType == ExpressionType.Parameter)
        {
            string columnName = node.Member.Name;
            if (node.Member.IsDefined(typeof(ColumnNameAttribute), false))
            {
                ColumnNameAttribute? columnNameAttribute = node.Member.GetCustomAttribute<ColumnNameAttribute>();
                columnName = columnNameAttribute?.Name ?? columnName;
            }
            _sqlQueue.Enqueue($" {columnName} ");
        }
        return node;
    }

    protected override Expression VisitUnary(UnaryExpression node)
    {
        if (node.NodeType == ExpressionType.Not && node.Operand.NodeType == ExpressionType.Call)
        {
            var methodExpression = node.Operand as MethodCallExpression;
            if (methodExpression != null && methodExpression.Method.Name == "IsNullOrEmpty")
            {
                Visit(methodExpression.Arguments[0]);
                _sqlQueue.Enqueue($" IS NOT NULL AND ");
                Visit(methodExpression.Arguments[0]);
                _sqlQueue.Enqueue($" <> '' ");
            }
        }
        return node;
    }

    protected override Expression VisitMethodCall(MethodCallExpression node)
    {
        Visit(node.Object);
        var method = node.Method;
        if (method.Name == "Contains")
        {
            _sqlQueue.Enqueue(" Like '%'+");
            Visit(node.Arguments[0]);
            _sqlQueue.Enqueue("+'%' ");
        }
        return node;
    }

    public string GetNodeTypeSql(ExpressionType type)
    {
        return type switch
        {
            ExpressionType.Equal => " = ",
            ExpressionType.AndAlso | ExpressionType.And => " AND ",
            ExpressionType.OrElse | ExpressionType.Or => " OR ",
            ExpressionType.Not => " NOT ",
            ExpressionType.NotEqual => " <> ",
            ExpressionType.GreaterThan => " > ",
            ExpressionType.GreaterThanOrEqual => " >= ",
            ExpressionType.LessThan => " < ",
            ExpressionType.LessThanOrEqual => " <= ",
            _ => " "
        };
    }
}
